﻿//======================================================================
//
//	Cross-Platform XNA Menu System
//  http://cpxms.codeplex.com/
//
//  All code and content licensed under Microsoft Public License (Ms-PL)	
//
//======================================================================
//
//	File:		MenuTree.cs
//
//	Author:		MJP
//
//	Date:		04/12/09
//
//======================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;
using System.ComponentModel;

using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

using CPXMenu;

using CPXMenuEditor.Forms;

using CPXMenuItem = CPXMenu.MenuItem;

namespace CPXMenuEditor.Controls
{
	/// <summary>
	/// A control that automatically updates a TreeView based on
	/// the state of a MenuProject, and also handles events generated
	/// as the result of user input.
	/// </summary>
    public partial class MenuTree : UserControl
    {
        private MenuEditorView menuEditorView;
        private MainFrm mainForm;
        private ItemProperties itemProperties;
        private MenuProject menuProject;
        private List<TreeNode> allNodes = new List<TreeNode>();
        ActionManager actionManager;

        private static ImageList globalIconList = null;

		/// <summary>
		/// Gets the global ImageList for icons (move to MainFrm?)
		/// </summary>
        public static ImageList GlobalIconList
        {
            get { return globalIconList; }
        }

		/// <summary>
		/// Gets the TreeView control
		/// </summary>
        public TreeView TreeView
        {
            get { return treeView; }
        }


		/// <summary>
		/// Gets or sets the associated MenuProject
		/// </summary>
        public MenuProject MenuProject
        {
            get { return menuProject; }
            set 
            {                 
                if (menuProject != value)
                    SetMenuProject(value);
            }
        }

		/// <summary>
		/// Creates a new MenuTree instance
		/// </summary>
        public MenuTree()
        {
            InitializeComponent();

            globalIconList = imageList;
        }

		/// <summary>
		/// Initializes the control
		/// </summary>
		/// <param name="mainForm">The MainFrm instance</param>
        public void Initialize(MainFrm mainForm)
        {
            this.mainForm = mainForm;
            menuEditorView = mainForm.MenuEditorView;
            itemProperties = mainForm.ItemProperties;
            actionManager = mainForm.ActionManager;

            actionManager.SelectionChanged += new ActionManagerDelegate(actionManager_SelectionChanged);
			actionManager.ProjectChanged += new ProjectChangedDelegate(actionManager_ProjectChanged);

            SetMenuProject(actionManager.MenuProject);
        }		        

		/// <summary>
		/// Gets the TreeNode that represents the specified MenuItem
		/// </summary>
		/// <param name="item">The item to whose node should be searched for</param>
		/// <returns>The TreeNode that represents the specifed item</returns>
        public TreeNode GetNodeByItem(CPXMenuItem item)
        {
            foreach (TreeNode node in allNodes)
                if (node.Tag == item)
                    return node;

            // Debug.Assert(false);
            return null;            
        }

		/// <summary>
		/// Sets the specified MenuProject as the current project.
		/// </summary>
		/// <param name="newProject">The new MenuProject to use</param>
        private void SetMenuProject(MenuProject newProject)
        {
            if (menuProject != null)
            {
                menuProject.ItemAdded -= menuProject_ItemAdded;
                menuProject.ItemRemoved -= menuProject_ItemRemoved;
                menuProject.CurrentPageChanged -= menuProject_CurrentPageChanged;
                menuProject.HomePageChanged -= menuProject_HomePageChanged;
            }

            menuProject = newProject;
            BuildTree();
            
            if (menuProject != null)
            {
                menuProject_CurrentPageChanged(menuProject, null, menuProject.CurrentPage);
                menuProject_HomePageChanged(menuProject, null, menuProject.HomePage);

                menuProject.ItemAdded += new ItemAddedOrRemovedDelegate(menuProject_ItemAdded);
                menuProject.ItemRemoved += new ItemAddedOrRemovedDelegate(menuProject_ItemRemoved);
                menuProject.CurrentPageChanged += new PageChangedDelegate(menuProject_CurrentPageChanged);
                menuProject.HomePageChanged += new PageChangedDelegate(menuProject_HomePageChanged);
            }
        }        
             
		/// <summary>
		/// Builds the TreeView for a MenuProject
		/// </summary>
        private void BuildTree()
        {
            treeView.Nodes.Clear();
            allNodes.Clear();

            if (menuProject != null)
                AddNodeForItem(menuProject, treeView.Nodes);
        }

		/// <summary>
		/// Adds a TreeNode for the specified MenuItem, and adds nodes for all children.
		/// </summary>
		/// <param name="item">The MenuItem to add a node for</param>
		/// <param name="nodeCollection">The collection to add the TreeNode to</param>
        private void AddNodeForItem(CPXMenuItem item, TreeNodeCollection nodeCollection)
        {
            if (GetNodeByItem(item) == null)
            {
                TreeNode node = new TreeNode(item.Name);
                node.Tag = item;
                AssignImageIndex(node, item);                
                node.ContextMenuStrip = mainForm.GetContextMenuForItem(item);
                nodeCollection.Add(node);
                allNodes.Add(node);

                item.NameChanged += new MenuItemDelegate(item_NameChanged);
                item.ChildrenReordered += new MenuItemDelegate(item_ChildrenReordered);

                foreach (CPXMenuItem child in item.Children)
                    AddNodeForItem(child, node.Nodes);
            }
        }        

		/// <summary>
		/// Removes the TreeNode for the specified item
		/// </summary>
		/// <param name="item">The MenuItem whose node should be removed</param>
        private void RemoveNodeForItem(CPXMenuItem item)
        {
            foreach (CPXMenuItem child in item.Children)
                RemoveNodeForItem(child);

            item.NameChanged -= item_NameChanged;
            
            TreeNode node = GetNodeByItem(item);
            if (node != null)
            {
                allNodes.Remove(node);
                node.Parent.Nodes.Remove(node);
            }
        }

		/// <summary>
		/// Assigns the image key for node/item pair
		/// </summary>
		/// <param name="node">The TreeNode representing the item</param>
		/// <param name="item">The item that the TreeNode represents</param>
        private void AssignImageIndex(TreeNode node, CPXMenuItem item)
        {
            if (item is Page && item == menuProject.HomePage)
                node.ImageKey = "HomePage";
            else
                node.ImageKey = CPXMenuItem.GetIconKey(item.GetType());

            node.SelectedImageKey = node.ImageKey;
        }

		/// <summary>
		/// Called when a MenuItem changes its name
		/// </summary>
		/// <param name="item">The MenuItem whose name changed</param>
        void item_NameChanged(CPXMenuItem item)
        {
            TreeNode node = GetNodeByItem(item);
            if (node != null)
                node.Text = item.Name;
        }

		/// <summary>
		/// Called when an item reorders its collection of children
		/// </summary>
		/// <param name="item">The MenuItem whose children were reordered</param>
        void item_ChildrenReordered(CPXMenuItem item)
        {
            TreeNode node = GetNodeByItem(item);
            if (node != null)
            {
                foreach (CPXMenuItem child in item.Children)
                {
                    RemoveNodeForItem(child);
                    AddNodeForItem(child, node.Nodes);
                }

                if (item is MenuProject)
                    menuProject_CurrentPageChanged(menuProject, menuProject.CurrentPage, menuProject.CurrentPage);
            }
        }

		/// <summary>
		/// Called when a MenuItem is removed from the project
		/// </summary>
		/// <param name="project">The project the item was removed from</param>
		/// <param name="parent">The former parent of the item</param>
		/// <param name="item">The item that was removed</param>
        void menuProject_ItemRemoved(MenuProject project, CPXMenuItem parent, CPXMenuItem item)
        {
            RemoveNodeForItem(item);
        }

		/// <summary>
		/// Called when a MenuItem is added to the project
		/// </summary>
		/// <param name="project">The project the item was added to</param>
		/// <param name="parent">The parent of the item</param>
		/// <param name="item">The item that was added</param>
        void menuProject_ItemAdded(MenuProject project, CPXMenuItem parent, CPXMenuItem item)
        {            
            TreeNode node = GetNodeByItem(parent);
            if (node != null)
                AddNodeForItem(item, node.Nodes);
        }

		/// <summary>
		/// Called when the CurrentPage property of the current MenuProject changes
		/// </summary>
		/// <param name="project">The current MenuProject</param>
		/// <param name="oldPage">The old CurrentPage</param>
		/// <param name="newPage">The new CurrentPage</param>
        void menuProject_CurrentPageChanged(MenuProject project, Page oldPage, Page newPage)
        {
            TreeNode node;

            if (oldPage != null)
            {
                node = GetNodeByItem(oldPage);
                if (node != null)
                {
                    node.NodeFont = new Font(treeView.Font, FontStyle.Regular);
                    node.Text = node.Text;
                }
            }

            if (newPage != null)
            {
                node = GetNodeByItem(newPage);
                if (node != null)
                {
                    node.NodeFont = new Font(treeView.Font, FontStyle.Bold);
                    node.Text = node.Text;
                }
            }            
        }

		/// <summary>
		/// Called when the HomePage property of the current MenuProject changes
		/// </summary>
		/// <param name="project">The current MenuProject</param>
		/// <param name="oldPage">The old HomePage</param>
		/// <param name="newPage">The new HomePage</param>
        void menuProject_HomePageChanged(MenuProject project, Page oldPage, Page newPage)
        {
            TreeNode node;

            if (newPage != null)
            {
                node = GetNodeByItem(newPage);
                if (node != null)
                {
                    AssignImageIndex(node, newPage);
                }
            }

            if (oldPage != null)
            {
                node = GetNodeByItem(oldPage);
                if (node != null)
                {
                    AssignImageIndex(node, oldPage);
                }
            }
        }

		/// <summary>
		/// Called after a node in the tree is selected
		/// </summary>
		/// <param name="sender">The TreeView that raised the event</param>
		/// <param name="e">Event arguments</param>
        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            treeView.AfterSelect -= treeView_AfterSelect;
            actionManager.SelectItem((CPXMenuItem)e.Node.Tag, false);
            treeView.AfterSelect += treeView_AfterSelect;
        }

		/// <summary>
		/// Called after a TreeNode's label is edited
		/// </summary>
		/// <param name="sender">The TreeView that raised the event</param>
		/// <param name="e">Event arguments</param>
        private void treeView_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
        {
            CPXMenuItem item = (CPXMenuItem)e.Node.Tag;

            if (!string.IsNullOrEmpty(e.Label))
            {
                if (item.VerifyNameIsUnique(e.Label))
                    actionManager.SetPropertyValue(item, "Name", e.Label);
                else
                {
                    MessageBox.Show(this, "Names must be unique for all items in the project", "CPXMenuEditor", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    e.CancelEdit = true;
                }
            }
            else
            {
                MessageBox.Show(this, "Names must have at least 1 character", "CPXMenuEditor", MessageBoxButtons.OK, MessageBoxIcon.Error);
                e.CancelEdit = true;
            }
        }

		/// <summary>
		/// Called after a TreeNode is double-clicked
		/// </summary>
		/// <param name="sender">The TreeView that raised the event</param>
		/// <param name="e">Event arguments</param>
        private void treeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            Page item = e.Node.Tag as Page;
            if (item != null && item != menuProject.CurrentPage)
            {
                actionManager.SetPropertyValue(menuProject, "CurrentPage", item);
                e.Node.Expand();
            }
        }

		/// <summary>
		/// Called after key is pressed by the user while the TreeView has focus
		/// </summary>
		/// <param name="sender">The TreeView that raised the event</param>
		/// <param name="e">Event arguments</param>
		private void treeView_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete && treeView.SelectedNode != null)
                actionManager.RemoveItem((CPXMenuItem)treeView.SelectedNode.Tag);
        }


		/// <summary>
		/// Called when the items currently selected by the ActionManager changes.
		/// </summary>
		/// <param name="manager"></param>
        void actionManager_SelectionChanged(ActionManager manager)
        {
            if (manager.SelectedItems.Count == 0)
                treeView.SelectedNode = null;
            else if (manager.SelectedItems.Count == 1)
            {
                treeView.AfterSelect -= treeView_AfterSelect;
                treeView.SelectedNode = GetNodeByItem(manager.SelectedItems[0]);
                treeView.AfterSelect += treeView_AfterSelect;
            }
            else
            {
                foreach (CPXMenuItem item in manager.SelectedItems)
                {
                    TreeNode node = GetNodeByItem(item);
                    if (node != null && node.Parent != null)
                        node.Parent.Expand();
                }
            }
        }

		/// <summary>
		/// Called when a new project is set for the ActionManager
		/// </summary>
		/// <param name="manager">The ActionManager</param>
		/// <param name="oldProject">The old MenuProject</param>
		/// <param name="newProject">The new MenuProject</param>
		void actionManager_ProjectChanged(ActionManager manager, MenuProject oldProject, MenuProject newProject)
		{
			SetMenuProject(newProject);
		}
    }
}
